package labox.innovation.gameserver.pathfinding;

import java.util.LinkedList;
import java.util.List;

import javolution.util.FastList;
import labox.innovation.config.Config;
import labox.innovation.gameserver.geodata.GeoData;
import labox.innovation.gameserver.pathfinding.cellnodes.CellPathFinding;
import labox.innovation.gameserver.pathfinding.geonodes.GeoPathFinding;
import labox.innovation.gameserver.pathfinding.utils.BinaryNodeHeap;
import labox.innovation.gameserver.pathfinding.utils.FastNodeList;

/**
 *
 * @author -Nemesiss-
 */
public abstract class PathFinding {
	public static PathFinding getInstance() {
		if (!Config.GEODATA_CELLFINDING) {
			return GeoPathFinding.getInstance();//·���Ѱ·�����ڴ���ģ���CPU���
		} else {
			return CellPathFinding.getInstance();//��Ԫ��Ѱ·��ֱ�Ӵ�geo����м��㣬��õ�Ѱ·����ߵ�CPU��ģ��Ƚϵ͵��ڴ����
		}
	}

	public abstract boolean pathNodesExist(short regionoffset);

	public abstract Node findPath(int id, int x, int y, int tx, int ty);

	public abstract Node[] readNeighbors(Node n, GeoData geo);

	/**
	 * ʹ��·��Ѱ·���㷨
	 * @param start
	 * @param end
	 * @return
	 */
	public Node searchByClosest2(Node start, Node end) {
		//TODO
		// Always continues checking from the closest to target non-blocked
		// node from to_visit list. There's extra length in path if needed
		// to go backwards/sideways but when moving generally forwards, this is extra fast
		// and accurate. And can reach insane distances (try it with 800 nodes..).
		// Minimum required node count would be around 300-400.
		// Generally returns a bit (only a bit) more intelligent looking routes than
		// the basic version. Not a true distance image (which would increase CPU
		// load) level of intelligence though.

		// List of Visited Nodes
		FastNodeList visited = FastNodeList.newInstance();

		// List of Nodes to Visit
		LinkedList<Node> to_visit = new LinkedList<Node>();
		to_visit.add(start);
		int targetx = end.getLoc().getNodeX();
		int targety = end.getLoc().getNodeY();
		int dx, dy;
		boolean added;
		int i = 0;
		while (i < 550) {
			Node node;
			try {
				node = to_visit.removeFirst();
			} catch (Exception e) {
				// No Path found
				return null;
			}
			if (node.equals(end)) //path found!
				return node;
			else {
				i++;
				visited.add(node);
				Node[] neighbors = node.getNeighbors();
				if (neighbors == null)
					continue;
				for (Node n : neighbors) {
					if (!visited.containsRev(n) && !to_visit.contains(n)) {
						added = false;
						n.setParent(node);
						dx = targetx - n.getLoc().getNodeX();
						dy = targety - n.getLoc().getNodeY();
						n.setCost(dx * dx + dy * dy);
						for (int index = 0; index < to_visit.size(); index++) {
							// supposed to find it quite early..
							if (to_visit.get(index).getCost() > n.getCost()) {
								to_visit.add(index, n);
								added = true;
								break;
							}
						}
						if (!added)
							to_visit.addLast(n);
					}
				}
			}
		}
		//No Path found
		return null;
	}

	/**
	 * ʹ��AstarѰ·���㷨
	 * @param start
	 * @param end
	 * @return
	 */
	public Node searchAStar(Node start, Node end) {

		int end_NodeX = end.getLoc().getNodeX();
		int end_NodeY = end.getLoc().getNodeY();

		//��������Ĺر��б�
		FastNodeList closeList = FastNodeList.newInstance();

		//�ȴ������Ŀ����б�
		BinaryNodeHeap openList = BinaryNodeHeap.newInstance();
		openList.add(start);

		boolean endFlag = false;
		Node node = null;
		while (!(endFlag || openList.isEmpty())) {
			node = openList.removeFirst();

			if (node.equals(end)) {
				endFlag = true;
				break;
			}
			if (!closeList.add(node))//����ر��б�,������ʧ��˵��·��Զ�˾ܾ��ƶ�
				break;

			for (Node n : node.getNeighbors()) {//��Χ�Ľڵ㣬���Ե�ͼ�߽�Ͳ����ߵĽڵ�
				if (n == null)
					continue;
				if (!closeList.containsRev(n)) {//���벻�ڹر��б���
					if (!openList.contains(n)) {
						n._parent = node;//���ø�

						//б��cost�Ǹ�2*10
						if (Math.abs(node._loc.getNodeX() - n._loc.getNodeX()) == Math.abs(node._loc.getNodeY() - n._loc.getNodeY()))
							n.g = node.g + 14;
						else
							n.g = node.g + 10;
						n.h = (Math.abs(end_NodeX - n._loc.getNodeX()) + Math.abs(end_NodeY - n._loc.getNodeY())) * 10;
						n.f = n.g + n.h;
						if (!openList.add(n)) {//���뿪���б?������ʧ��˵��·��Զ�˾ܾ��ƶ�
							endFlag = true;
							node = null;
							break;
						}
					} else {
						int g;
						//б��cost�Ǹ�2*10
						if (Math.abs(node._loc.getNodeX() - n._loc.getNodeX()) == Math.abs(node._loc.getNodeY() - n._loc.getNodeY()))
							g = node.g + 14;
						else
							g = node.g + 10;
						if (n.g > g) {
							n._parent = node;
							n.g = g;
							n.f = n.g + n.h;
						}
					}
				}
			}
		}
		//Ѱ·��ɻ��ն���Ѷ���
		BinaryNodeHeap.recycle(openList);
		//Ѱ·��ɻ��տ����б����
		FastNodeList.recycle(closeList);
		if (endFlag)
			return node;
		return null;
	}

	public List<AbstractNodeLoc> constructPath(Node node) {
		LinkedList<AbstractNodeLoc> path = new LinkedList<AbstractNodeLoc>();
		int previousdirectionx = -1000;
		int previousdirectiony = -1000;
		int directionx;
		int directiony;
		while (node.getParent() != null) {
			// only add a new route point if moving direction changes
			if (node.getParent().getParent() != null // to check and clean diagonal movement
					&& Math.abs(node.getLoc().getNodeX() - node.getParent().getParent().getLoc().getNodeX()) == 1 && Math.abs(node.getLoc().getNodeY() - node.getParent().getParent().getLoc().getNodeY()) == 1) {
				directionx = node.getLoc().getNodeX() - node.getParent().getParent().getLoc().getNodeX();
				directiony = node.getLoc().getNodeY() - node.getParent().getParent().getLoc().getNodeY();
			} else {
				directionx = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX();
				directiony = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY();
			}
			if (directionx != previousdirectionx || directiony != previousdirectiony) {
				previousdirectionx = directionx;
				previousdirectiony = directiony;
				path.addFirst(node.getLoc());
			}
			node = node.getParent();
		}
		// then LOS based filtering to reduce the number of route points
		if (path.size() > 4) {
			//System.out.println("pathsize:"+path.size());
			List<Integer> valueList = new FastList<Integer>();
			for (int index = 0; index < path.size() - 3; index = index + 3) {
				//System.out.println("Attempt filter");
				//				if (GeoData.getInstance().canMoveFromToTarget(path.get(index).getX(), path.get(index).getY(), path.get(index + 3).getX(), path.get(index + 3).getY(), 0)) {
				//					//System.out.println("filtering i:"+(index+1));
				//					valueList.add(index + 1);
				//					valueList.add(index + 2);
				//				}
			}
			for (int index = valueList.size() - 1; index >= 0; index--) {
				path.remove(valueList.get(index).intValue());
			}
			//System.out.println("pathsize:"+path.size());
		}
		return path;
	}

	public List<AbstractNodeLoc> constructPath2(Node node) {
		LinkedList<AbstractNodeLoc> path = new LinkedList<AbstractNodeLoc>();
		int previousdirectionx = -1000;
		int previousdirectiony = -1000;
		int directionx;
		int directiony;
		while (node.getParent() != null) {
			// only add a new route point if moving direction changes
			directionx = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX();
			directiony = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY();
			if (directionx != previousdirectionx || directiony != previousdirectiony) {
				previousdirectionx = directionx;
				previousdirectiony = directiony;
				path.addFirst(node.getLoc());
			}
			node = node.getParent();
		}
		return path;
	}
}
